﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using GBPVRX2.UiSupport;
using System.Xml;
using GBPVRLibrary.UtilityLibrary;
using System.Drawing;
using System.Collections;
using System.Management;
using System.DirectoryServices;
using System.Runtime.InteropServices;
using GBPVR.Public;

namespace GBPVRLibrary.ControlLibrary
{
    /// <summary>
    /// An open file box (similar to an OpenFileDialog) that can browse your local file system and your connected network looking for a folder/file
    /// </summary>
    public class OpenFileBox:GuiElement
    {
        public bool DisplayFiles { get; set; }
        public string FilterExpression { get; set; }

        public delegate void FileSelected_EventHandler(string Filename);
        public event FileSelected_EventHandler FileSelected;

        private OpenFileItem Root { get; set; }
        private Stack<OpenFileItem> Items { get; set; }
        private OpenFileItem CurrentItem { get { return Items.Count == 0 ? null : Items.Peek(); } }

        private int Rows { get; set; }
        private int SelectedIndex { get; set; }
        private int StartIndex { get; set; }
        private PointF ItemsLocation { get; set; }
        private PointF AddItemLocation { get; set; }
        private float VerticalSpacing { get; set; }
             
        private List<UiStatic> UiItems { get; set; }
        private UiStatic SelectedUiItem { get; set; }
        private List<Hashtable> Parameters { get; set; }
        private Hashtable SelectedParameters { get; set; }
        private UiStatic Background { get; set; }

        private List<UiStatic> UiAddItems { get; set; }
        private UiStatic SelectedUiAddItem { get; set; }

        private bool AddButtonSelected { get; set; }
        protected System.ComponentModel.BackgroundWorker BackgroundLoader { get; set; }
        private GBPVRUiElement LoadingImage { get; set; }
        private DateTime LastFlip = DateTime.MinValue;

        public OpenFileBox()
        {
            this.Visible = false;
            this.UiItems = new List<UiStatic>();
            this.Parameters = new List<Hashtable>();

            this.ItemsLocation = SkinHelper.getPlacementAttributePos("OpenFileBox", "itemsLocation");
            this.AddItemLocation = SkinHelper.getPlacementAttributePos("OpenFileBox", "addItemLocation");
            this.Rows = int.Parse(SkinHelper.getPlacementAttributeElementValue("OpenFileBox", "rows"));
            this.VerticalSpacing = float.Parse(SkinHelper.getPlacementAttributeElementValue("OpenFileBox", "verticalSpacing"));
            this.UiAddItems = new List<UiStatic>();
            for (int i = 0; i < Rows; i++)
            {
                Hashtable param = new Hashtable();
                UiStatic ui = new UiStatic(SkinHelper, "OpenFileBox-Item-Normal", param);
                ui.SetLocation(new PointF(this.ItemsLocation.X, this.ItemsLocation.Y + (i * VerticalSpacing)));
                Parameters.Add(param);
                UiItems.Add(ui);

                UiStatic uiAdd = new UiStatic(SkinHelper, "OpenFileBox-Add-Item-Normal", new Hashtable());
                uiAdd.SetLocation(new PointF(this.AddItemLocation.X, this.AddItemLocation.Y + (i * VerticalSpacing)));
                UiAddItems.Add(uiAdd);
            }
            SelectedParameters = new Hashtable();
            SelectedUiItem = new UiStatic(SkinHelper, "OpenFileBox-Item-Selected", SelectedParameters);
            SelectedUiAddItem = new UiStatic(SkinHelper, "OpenFileBox-Add-Item-Selected", new Hashtable());
            this.Background = new UiStatic(SkinHelper, "OpenFileBox", new Hashtable());
            LoadingImage = new GBPVRUiElement("OpenFileBox-Loading", GuiElement.SkinHelper.getPlacementRect("OpenFileBox-Loading"), GuiElement.SkinHelper.getNamedImage("OpenFileBox-Loading", new Hashtable()));
        }

        public void Show(string Title)
        {
            Hashtable backgroundParameters = new Hashtable();
            backgroundParameters["@Title"] = Title;
            Background.SetArgs(backgroundParameters);
            Root = new OpenFileItem() { Name = "Computer", FullName = "Computer", FileType = OpenFileItem.OpenFileType.Root };
            //Root.Load(this.DisplayFiles);
            this.Items = new Stack<OpenFileItem>();
            this.SelectedIndex = 0;
            this.StartIndex = 0;
            this.Visible = true;
            LoadDirectory();
        }

        private void LoadDirectory()
        {
            GuiElement.NeedsRendering = true;
            LoadingImage.textureTopLeft = new PointF(0, 0);
            LoadingImage.textureTopRight = new PointF(1, 0);
            LoadingImage.textureBottomRight = new PointF(1, 1);
            LoadingImage.textureBottomLeft = new PointF(0, 1);

            BackgroundLoader = new System.ComponentModel.BackgroundWorker();
            BackgroundLoader.DoWork += new System.ComponentModel.DoWorkEventHandler(BackgroundLoader_DoWork);
            BackgroundLoader.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(BackgroundLoader_RunWorkerCompleted);
            BackgroundLoader.WorkerSupportsCancellation = true;
            BackgroundLoader.RunWorkerAsync();
            GuiElement.NeedsRendering = true;
        }

        void BackgroundLoader_RunWorkerCompleted(object sender, System.ComponentModel.RunWorkerCompletedEventArgs e)
        {
            if (!e.Cancelled)
            {
            }
            this.BackgroundLoader = null;
            GuiElement.NeedsRendering = true;
        }

        void BackgroundLoader_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
        {
            if (this.CurrentItem == null)
            {
                Root.Load(this.DisplayFiles);
                this.Items.Push(Root);
            }
            else
            {
                this.CurrentItem.Items[SelectedIndex].Load(this.DisplayFiles);
                this.CurrentItem.Items[SelectedIndex].SelectedIndex = SelectedIndex;
                this.CurrentItem.Items[SelectedIndex].StartIndex = StartIndex;
                this.Items.Push(this.CurrentItem.Items[SelectedIndex]);
            }
            this.StartIndex = 0;
            this.SelectedIndex = 0;
        }

        protected override void Render(System.Collections.ArrayList RenderList, ref bool RequiresMoreRendering)
        {
            RenderList.AddRange(Background.GetRenderList());

            if (this.BackgroundLoader != null && this.BackgroundLoader.IsBusy)
            {
                if (LastFlip < DateTime.Now.AddMilliseconds(-250))
                {
                    LoadingImage.textureTopLeft = GBPVRLibrary.UtilityLibrary.Drawing.Utilities.RotatePoint(LoadingImage.textureTopLeft, new PointF(0.5f, 0.5f), -45);
                    LoadingImage.textureTopRight = GBPVRLibrary.UtilityLibrary.Drawing.Utilities.RotatePoint(LoadingImage.textureTopRight, new PointF(0.5f, 0.5f), -45);
                    LoadingImage.textureBottomRight = GBPVRLibrary.UtilityLibrary.Drawing.Utilities.RotatePoint(LoadingImage.textureBottomRight, new PointF(0.5f, 0.5f), -45);
                    LoadingImage.textureBottomLeft = GBPVRLibrary.UtilityLibrary.Drawing.Utilities.RotatePoint(LoadingImage.textureBottomLeft, new PointF(0.5f, 0.5f), -45);
                    LastFlip = DateTime.Now;
                }
                RenderList.Add(LoadingImage);
                GuiElement.NeedsRendering = true; // because we are animating
            }
            else
            {
                if (this.CurrentItem == null || this.CurrentItem.Items == null || this.CurrentItem.Items.Count == 0)
                    return;
                for (int i = 0; i < this.UiItems.Count; i++)
                {
                    int index = i + StartIndex;
                    if (index >= this.CurrentItem.Items.Count)
                        break;
                    if (index == SelectedIndex)
                    {
                        SelectedParameters["@Text"] = CurrentItem.Items[index].Name;
                        SelectedParameters["@Icon"] = CurrentItem.Items[index].Icon;
                        SelectedUiItem.SetArgs(SelectedParameters);
                        SelectedUiItem.SetLocation(new PointF(this.ItemsLocation.X, this.ItemsLocation.Y + (i * VerticalSpacing)));
                        RenderList.AddRange(SelectedUiItem.GetRenderList());
                        if (CurrentItem.Items[index].Addable)
                        {
                            if (AddButtonSelected)
                            {
                                SelectedUiAddItem.SetLocation(new PointF(this.AddItemLocation.X, this.AddItemLocation.Y + (i * VerticalSpacing)));
                                RenderList.AddRange(SelectedUiAddItem.GetRenderList());
                            }
                            else
                            {
                                RenderList.AddRange(UiAddItems[i].GetRenderList());
                            }
                        }
                    }
                    else
                    {
                        Hashtable parameters = Parameters[i];
                        parameters["@Text"] = CurrentItem.Items[index].Name;
                        parameters["@Icon"] = CurrentItem.Items[index].Icon;
                        UiItems[i].SetArgs(parameters);
                        RenderList.AddRange(UiItems[i].GetRenderList());
                        if (CurrentItem.Items[index].Addable)
                            RenderList.AddRange(UiAddItems[i].GetRenderList());
                    }
                }
            }
        }

        private void PopList()
        {
            this.StartIndex = this.CurrentItem.StartIndex;
            this.SelectedIndex = this.CurrentItem.SelectedIndex;
            foreach (OpenFileItem item in this.CurrentItem.Items)
                item.Dispose();
            this.CurrentItem.Items.Clear();
            this.Items.Pop();
        }

        public override bool OnKeyDown(System.Windows.Forms.KeyEventArgs e)
        {
            switch (KeyMapper.GetControlKey(e))
            {
                case KeyMapper.ControlKey.Back:
                    if (this.Items.Count <= 1)
                        this.Visible = false;
                    else
                        PopList();
                    return true;
                case KeyMapper.ControlKey.Enter:
                    if (this.AddButtonSelected)
                    {
                        if (FileSelected != null)
                            FileSelected(this.CurrentItem.Items[this.SelectedIndex].FullName);
                        this.Visible = false;
                    }
                    else
                    {
                        if (this.CurrentItem.Items[SelectedIndex].FileType == OpenFileItem.OpenFileType.File)
                            return true;
                        else if (this.CurrentItem.Items[SelectedIndex].FileType == OpenFileItem.OpenFileType.Parent)
                        {
                            PopList();
                            return true;
                        }
                        LoadDirectory();
                    }
                    return true;
                case KeyMapper.ControlKey.DirectionDown:
                    if (this.SelectedIndex + 1 < this.CurrentItem.Items.Count)
                    {
                        this.SelectedIndex++;
                        if (this.StartIndex + Rows <= this.SelectedIndex)
                            this.StartIndex = (this.SelectedIndex - this.Rows) + 1;
                    }
                    else
                    {
                        this.SelectedIndex = 0;
                        this.StartIndex = 0;
                    }
                    this.AddButtonSelected = false;
                    return true;
                case KeyMapper.ControlKey.DirectionUp:
                    if (this.SelectedIndex - 1 >= 0)
                    {
                        this.SelectedIndex--;
                        if (this.StartIndex > this.SelectedIndex)
                            this.StartIndex = this.SelectedIndex;
                    }
                    else
                    {
                        this.SelectedIndex = this.CurrentItem.Items.Count - 1;
                        this.StartIndex = this.CurrentItem.Items.Count - Rows;
                        if (this.StartIndex < 0)
                            this.StartIndex = 0;
                    }
                    this.AddButtonSelected = false;
                    return true;
                case KeyMapper.ControlKey.DirectionRight:
                case KeyMapper.ControlKey.DirectionLeft:
                    this.AddButtonSelected = !this.AddButtonSelected && this.CurrentItem.Items[this.SelectedIndex].Addable;                    
                    return true;
            }
            return base.OnKeyDown(e);                
        }
    }

    class OpenFileItem:IDisposable
    {
        public string FullName { get; set; }
        public List<OpenFileItem> Items { get; set; }
        public enum OpenFileType { Root, Computer, File, Directory, HardDrive, Workgroup, RemovableDrive, OpticalDrive, NetworkDrive, Parent }
        private OpenFileType _FileType;
        public OpenFileType FileType
        {
            get
            {
                return _FileType;
            }
            set
            {
                this._FileType = value;
                System.Drawing.Icon icon = null;
                if (value == OpenFileType.Directory)
                    icon = GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.GetFolderIcon(GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.IconSize.Large, GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.FolderType.Closed);
                else if (value == OpenFileType.Parent)
                    icon = GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.GetFolderIcon(GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.IconSize.Large, GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.FolderType.Open);
                else if (value == OpenFileType.Workgroup)
                    icon = GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.GetShellIcon(GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.IconType.NetworkWorkgroup, GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.IconSize.Large);
                else if(value == OpenFileType.Computer)
                    icon = GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.GetShellIcon(GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.IconType.Computer, GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.IconSize.Large);
                else
                    icon = GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.GetFileIcon(FullName, GBPVRLibrary.UtilityLibrary.ComputerSystem.ShellIconLoader.IconSize.Large, false);
                if (icon != null)
                {
                    this.Icon = icon.ToBitmap();
                    icon.Dispose();
                }
            }
        }
        public string Name { get; set; }
        public int SelectedIndex { get; set; }
        public int StartIndex { get; set; }
        private object Argument{get;set;}
        public Image Icon { get; set; }

        public OpenFileItem()
        {

        }

        public OpenFileItem(string Name, string FullName, OpenFileType FileType)
        {
            this.Name = Name;
            this.FullName = FullName;
            this.FileType = FileType;
        }

        public bool Addable
        {
            get
            {
                return this.FileType != OpenFileType.Computer && this.FileType != OpenFileType.Parent && this.FileType != OpenFileType.Root && this.FileType != OpenFileType.Workgroup;
            }
        }

        public void Load(bool LoadFiles)
        {
            if (this.FileType == OpenFileType.Root)
            {
                // load drives, networks, etc
                this.Items = (from drive in DriveInfo.GetDrives() select new OpenFileItem(drive.Name, drive.RootDirectory.FullName, LoadFileType(drive.DriveType))).ToList();
                
                DirectoryEntry root = new DirectoryEntry("WinNT:");
                foreach (System.DirectoryServices.DirectoryEntry dDom in root.Children)
                {
                    this.Items.Add(new OpenFileItem(dDom.Name, dDom.Name, OpenFileType.Workgroup) { Argument = dDom });
                }
            }
            else if (this.FileType == OpenFileType.Workgroup)
            {
                this.Items = new List<OpenFileItem>();
                this.Items.Add(new OpenFileItem() { Name = "..", FileType = OpenFileType.Parent });
                System.DirectoryServices.DirectoryEntry dDom = this.Argument as System.DirectoryServices.DirectoryEntry;
                if(dDom == null)
                    return;
                foreach (System.DirectoryServices.DirectoryEntry dPC in dDom.Children)
                {
                    if(dPC.SchemaClassName == "Computer")
                        this.Items.Add(new OpenFileItem(dPC.Name, dPC.Name, OpenFileType.Computer) { Argument = dPC });
                }
            }
            else if(this.FileType == OpenFileType.Computer)
            {
                this.Items = EnumerateSharesNT(this.Name);                
                this.Items.Insert(0, new OpenFileItem() { Name = "..", FileType = OpenFileType.Parent });
            }
            else
            {
                this.Items = new List<OpenFileItem>();
                this.Items.Add(new OpenFileItem() { Name = "..", FileType = OpenFileType.Parent });
                DirectoryInfo dir = new DirectoryInfo(this.FullName);
                if (!dir.Exists)
                {
                    return;
                }
                foreach (DirectoryInfo d in dir.GetDirectories())
                    this.Items.Add(new OpenFileItem(d.Name, d.FullName, OpenFileType.Directory));
                if (LoadFiles)
                {
                    foreach (FileInfo fi in dir.GetFiles())
                    {
                        this.Items.Add(new OpenFileItem(fi.Name, fi.FullName, OpenFileType.File));
                    }
                }
            }
        }

        private OpenFileType LoadFileType(DriveType DriveType)
        {
            switch (DriveType)
            {
                case DriveType.CDRom:
                    return OpenFileType.OpticalDrive;
                case DriveType.Network:
                    return OpenFileType.NetworkDrive;
                case DriveType.Ram:
                    return OpenFileType.RemovableDrive;
                case DriveType.Removable:
                    return OpenFileType.RemovableDrive;
                case DriveType.Fixed:
                    return OpenFileType.HardDrive;                
                default:
                    return OpenFileType.Directory;
            }
        }

        /// <summary>Enumerate shares (NT)</summary>
        [DllImport("netapi32", CharSet = CharSet.Unicode)]
        protected static extern int NetShareEnum(string lpServerName, int dwLevel, out IntPtr lpBuffer, int dwPrefMaxLen, out int entriesRead, out int totalEntries, ref int hResume);

        /// <summary>Free the buffer (NT)</summary>
        [DllImport("netapi32")]
        protected static extern int NetApiBufferFree(IntPtr lpBuffer);

        /// <summary>Share information, NT, level 2</summary>
        /// <remarks>
        /// Requires admin rights to work. 
        /// </remarks>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        protected struct SHARE_INFO_2
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string NetName;
            public ShareType ShareType;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string Remark;
            public int Permissions;
            public int MaxUsers;
            public int CurrentUsers;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string Path;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string Password;
        }

        /// <summary>Share information, NT, level 1</summary>
        /// <remarks>
        /// Fallback when no admin rights.
        /// </remarks>
        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        protected struct SHARE_INFO_1
        {
            [MarshalAs(UnmanagedType.LPWStr)]
            public string NetName;
            public ShareType ShareType;
            [MarshalAs(UnmanagedType.LPWStr)]
            public string Remark;
        }

        /// <summary>Maximum path length</summary>
        protected const int MAX_PATH = 260;
        /// <summary>No error</summary>
        protected const int NO_ERROR = 0;
        /// <summary>Access denied</summary>
        protected const int ERROR_ACCESS_DENIED = 5;
        /// <summary>Access denied</summary>
        protected const int ERROR_WRONG_LEVEL = 124;
        /// <summary>More data available</summary>
        protected const int ERROR_MORE_DATA = 234;
        /// <summary>Not connected</summary>
        protected const int ERROR_NOT_CONNECTED = 2250;
        /// <summary>Level 1</summary>
        protected const int UNIVERSAL_NAME_INFO_LEVEL = 1;
        /// <summary>Max extries (9x)</summary>
        protected const int MAX_SI50_ENTRIES = 20;

        /// <summary>
        /// Type of share
        /// </summary>
        [Flags]
        public enum ShareType
        {
            /// <summary>Disk share</summary>
            Disk = 0,
            /// <summary>Printer share</summary>
            Printer = 1,
            /// <summary>Device share</summary>
            Device = 2,
            /// <summary>IPC share</summary>
            IPC = 3,
            /// <summary>Special share</summary>
            Special = -2147483648, // 0x80000000,
        }

        /// <summary>
        /// Enumerates the shares on Windows NT
        /// </summary>
        /// <param name="Server">The server name</param>
        protected static List<OpenFileItem> EnumerateSharesNT(string Server)
        {
            List<OpenFileItem> items = new List<OpenFileItem>();
            int level = 2;
            int entriesRead, totalEntries, nRet, hResume = 0;
            IntPtr pBuffer = IntPtr.Zero;

            try
            {
                nRet = NetShareEnum(Server, level, out pBuffer, -1,
                    out entriesRead, out totalEntries, ref hResume);

                if (ERROR_ACCESS_DENIED == nRet)
                {
                    //Need admin for level 2, drop to level 1
                    level = 1;
                    nRet = NetShareEnum(Server, level, out pBuffer, -1,
                        out entriesRead, out totalEntries, ref hResume);
                }

                if (NO_ERROR == nRet && entriesRead > 0)
                {
                    Type t = (2 == level) ? typeof(SHARE_INFO_2) : typeof(SHARE_INFO_1);
                    int offset = Marshal.SizeOf(t);

                    for (int i = 0, lpItem = pBuffer.ToInt32(); i < entriesRead; i++, lpItem += offset)
                    {
                        IntPtr pItem = new IntPtr(lpItem);
                        if (1 == level)
                        {
                            SHARE_INFO_1 si = (SHARE_INFO_1)Marshal.PtrToStructure(pItem, t);      
                            if(si.ShareType == ShareType.Disk)
                                items.Add(new OpenFileItem(si.NetName, String.Format(@"\\{0}\{1}", Server, si.NetName), OpenFileType.Directory));//si.ShareType, si.Remark);
                        }
                        else
                        {
                            SHARE_INFO_2 si = (SHARE_INFO_2)Marshal.PtrToStructure(pItem, t);
                            if (si.ShareType == ShareType.Disk)
                                items.Add(new OpenFileItem(si.NetName, String.Format(@"\\{0}\{1}", Server, si.NetName), OpenFileType.Directory));//items.Add(si.NetName, si.Path, si.ShareType, si.Remark);
                        }
                    }
                }
                return items;
            }
            finally
            {
                // Clean up buffer allocated by system
                if (IntPtr.Zero != pBuffer)
                    NetApiBufferFree(pBuffer);
            }
        }

        #region IDisposable Members

        public void Dispose()
        {
            if (this.Icon != null)
                this.Icon.Dispose();
            this.Icon = null;
        }

        #endregion
    }

}
